package edu.amm.fanal.api;

/**
 * Вещественнозначная функция вещественных переменных.
 * 
 * @author Иванов Илья
 * @since 2013-05-10
 *
 */
public abstract class Function {
	
	private int dim;
	
	/**
	 * @param dim Число переменных функции.
	 */
	public Function(int dim) {
		if (dim < 1)
			throw new RuntimeException("Число переменных функции должно быть положительным");
		
		this.dim = dim;
	}
	
	/**
	 * @return Число переменных функции.
	 */
	public int getDim() {
		return dim;
	}
	
	/**
	 * @param x Аргумент функции.
	 * @return Значение функции на аргументе.
	 */
	public double evaluate(double... x) {
		if (x == null || x.length != dim)
			throw new RuntimeException("Некорректный аргумент функции");
		
		return getValue(x);
	}
	
	/**
	 * @param variableNumber Номер переменной, начиная с 0.
	 * @return Производная функции по заданной переменной (опциональная операция).
	 */
	public Function differentiate(int variableNumber) {
		if (variableNumber < 0 || variableNumber >= getDim())
			throw new RuntimeException("Некорректный номер переменной");
		
		return getDerivative(variableNumber);
	}
	
	/**
	 * @param x Аргумент функции.
	 * @return Значение функции на аргументе.
	 */
	protected abstract double getValue(double... x);
	
	/**
	 * @param variableNumber Номер переменной, начиная с единицы.
	 * @return Производная функции по заданной переменной (опциональная операция).
	 */
	protected Function getDerivative(int variableNumber) {
		throw new UnsupportedOperationException("Производная в явном виде не задана для этой функции");
	}
	
	/**
	 * Проверяет функцию на соответствие количеству аргументов.
	 * @param function Функция.
	 * @param name Имя функции.
	 * @param dim Количество аргументов.
	 */
	public static void checkFunction(Function function, String name, int dim) {
		if (function == null)
			throw new RuntimeException(String.format(NO_FUNCTION, name));
		if (function.getDim() != dim)
			throw new RuntimeException(String.format(FUNCTION_MUST_BE, name, dim));
	}
	
	/**
	 * @param dim Число переменных функции.
	 * @param value Значение функции.
	 * @return Тождественная функция заданного числа переменных.
	 * Функция имеет первые производные.
	 */
	public static Function createIdentical(int dim, final double value) {
		return createIdentical(dim, value, 0);
	}
	
	/**
	 * Вычисляет разность функций одного числа переменных.
	 * 
	 * @param left Левый операнд.
	 * @param right Правый операнд.
	 * @return Разность функций <code>left - right</code>.
	 */
	public static Function subtraction(final Function left, final Function right) {
		if (left == null || right == null)
			throw new RuntimeException("Не задан один или более операндов для вычисления разности функций");
		if (left.getDim() != right.getDim())
			throw new RuntimeException("Операнды разности функций должны зависеть от одного числа переменных");
		
		return new Function(left.getDim()) {
			
			protected double getValue(double... x) {
				return left.evaluate(x) - right.evaluate(x);
			}
		};
	}
	
	private static Function createIdentical(int dim, final double value, int derivative) {
		if (derivative == 0)
			return new Function(dim) {
				
				private Function derivative = Function.createIdentical(getDim(), 0, 1);
				
				protected double getValue(double... x) {
					return value;
				}
				
				protected Function getDerivative(int variableNumber) {
					return derivative;
				}
			};
		else
			return new Function(dim) {
				
				protected double getValue(double... x) {
					return value;
				}
			};
	}
	
	private static final String NO_FUNCTION = "Не задана функция %s";
	private static final String FUNCTION_MUST_BE = "Функция %s должна быть функцией %d переменных";
}